package cn.iocoder.yudao.framework.web.core.handler;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.biz.logger.ApiErrorLogCommonApi;
import cn.iocoder.yudao.framework.common.biz.logger.dto.ApiErrorLogCreateReqDTO;
import cn.iocoder.yudao.framework.common.exception.KnownServiceException;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyWrapper;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintDeclarationException;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;

/**
 * <pre>
 * OOoO0OOoO0OOOooo0oOOOO0OOOOO0oooOO0ooOOO0Ooooo0OOOOo0ooooO0OOooo0Ooooo0OOOOO
 * FIXME 覆盖芋道原版的 {@link GlobalExceptionHandler}
 * OOoO0OOoO0OOOooo0oOOOO0OOOOO0oooOO0ooOOO0Ooooo0OOOOo0ooooO0OOooo0Ooooo0OOOOO
 * </pre>
 *
 * @author 山野羡民（1032694760@qq.com）
 * @since 2025/01/13
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 日志 traceId 请求头字段
     */
    public static final String HEADER_TRACE_ID = "X-Trace-Id";
    /**
     * 忽略的 ServiceException 错误提示，避免打印过多 logger
     */
    public static final Set<String> IGNORE_ERROR_MESSAGES = SetUtils.asSet(
            "无效的刷新令牌", "Token 已过期"
    );
    public static final String PROPERTY_EXCEPTION_LOG_ENABLE = "xianmin.exception-log.enable";

    @SuppressWarnings("unused")
    public CommonResult<?> allExceptionHandler(HttpServletRequest request, Throwable ex) {
        // 处理所有异常，主要是提供给 Filter 使用，因为 Filter 不走 SpringMVC 的流程，但是我们又需要兜底处理异常。
        // 这里提供一个全量的异常处理过程，保持逻辑统一。
        if (ex instanceof HttpMessageConversionException httpMessageConversionException) {
            return httpMessageConversionExceptionHandler(httpMessageConversionException);
        }
        if (ex instanceof KnownServiceException knownServiceException) {
            return knownServiceExceptionHandler(knownServiceException);
        }
        if (ex instanceof ServiceException serviceException) {
            return serviceExceptionHandler(serviceException);
        }
        if (ex instanceof MissingServletRequestParameterException) {
            return missingServletRequestParameterExceptionHandler((MissingServletRequestParameterException) ex);
        }
        if (ex instanceof MethodArgumentTypeMismatchException) {
            return methodArgumentTypeMismatchExceptionHandler((MethodArgumentTypeMismatchException) ex);
        }
        if (ex instanceof MethodArgumentNotValidException) {
            return methodArgumentNotValidExceptionExceptionHandler((MethodArgumentNotValidException) ex);
        }
        if (ex instanceof BindException) {
            return bindExceptionHandler((BindException) ex);
        }
        if (ex instanceof ConstraintViolationException) {
            return constraintViolationExceptionHandler((ConstraintViolationException) ex);
        }
        if (ex instanceof ValidationException) {
            return validationException((ValidationException) ex);
        }
        if (ex instanceof NoHandlerFoundException) {
            return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex);
        }
        if (ex instanceof NoResourceFoundException) {
            return noResourceFoundExceptionHandler((NoResourceFoundException) ex);
        }
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
        }
        if (ex instanceof HttpMediaTypeNotSupportedException) {
            return httpMediaTypeNotSupportedExceptionHandler((HttpMediaTypeNotSupportedException) ex);
        }
        if (ex instanceof AccessDeniedException) {
            return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
        }
        return defaultExceptionHandler(request, ex);
    }

    /**
     * 处理 SpringMVC 请求参数缺失
     * 例如说，接口上设置了 @RequestParam("xx") 参数，结果并未传递 xx 参数
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public CommonResult<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
        log.warn("[missingServletRequestParameterExceptionHandler] {}", ex.getMessage());
        return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()));
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     * 例如说，接口上设置了 @RequestParam("xx") 参数为 Integer，结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public CommonResult<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
        log.warn("[methodArgumentTypeMismatchExceptionHandler] {}", ex.getMessage());
        if (StrUtil.contains(ex.getMessage(), "Failed to convert")) {
            return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误");
        }
        return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误：%s", ex.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public CommonResult<?> methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
        log.warn("[methodArgumentNotValidExceptionExceptionHandler] {}", ex.getMessage());
        FieldError fieldError = ex.getBindingResult().getFieldError();
        if (fieldError == null) {
            // 组合校验，参考自 https://t.zsxq.com/3HVTx
            List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
            if (CollUtil.isNotEmpty(allErrors)) {
                return CommonResult.error(BAD_REQUEST.getCode(), allErrors.get(0).getDefaultMessage());
            }
            return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误");
        }
        if (StrUtil.contains(fieldError.getDefaultMessage(), "Failed to convert")) {
            return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误");
        }
        return CommonResult.error(BAD_REQUEST.getCode(), fieldError.getDefaultMessage());
    }

    /**
     * 处理 SpringMVC 参数绑定不正确，本质上也是通过 Validator 校验
     */
    @ExceptionHandler(BindException.class)
    public CommonResult<?> bindExceptionHandler(BindException ex) {
        log.warn("[handleBindException] {}", ex.getMessage());
        FieldError fieldError = ex.getFieldError();
        if (fieldError == null) {
            // 组合校验，参考自 https://t.zsxq.com/3HVTx
            List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
            if (CollUtil.isNotEmpty(allErrors)) {
                return CommonResult.error(BAD_REQUEST.getCode(), allErrors.get(0).getDefaultMessage());
            }
            return CommonResult.error(BAD_REQUEST.getCode(), "请求参数无效");
        }
        return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数无效：%s", fieldError.getDefaultMessage()));
    }

    /**
     * 处理 Validator 校验不通过产生的异常
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResult<?> constraintViolationExceptionHandler(ConstraintViolationException ex) {
        log.warn("[constraintViolationExceptionHandler] {}", ex.getMessage());
        ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
        return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数验证失败：%s", constraintViolation.getMessage()));
    }

    /**
     * 处理参数校验时抛出的异常，如 {@link ConstraintDeclarationException}
     */
    @ExceptionHandler(value = ValidationException.class)
    public CommonResult<?> validationException(ValidationException ex) {
        log.warn("[validationException] ", ex);
        return CommonResult.error(BAD_REQUEST.getCode(), ex.getMessage());
    }

    /**
     * 处理 SpringMVC 请求地址不存在
     * 注意，它需要设置如下两个配置项：
     * 1. spring.mvc.throw-exception-if-no-handler-found 为 true
     * 2. spring.mvc.static-path-pattern 为 /statics/**
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public CommonResult<?> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
        log.warn("[noHandlerFoundExceptionHandler] {}", ex.getMessage());
        return CommonResult.error(NOT_FOUND.getCode(), String.format("请求地址不存在：%s", ex.getRequestURL()));
    }

    /**
     * 处理 SpringMVC 请求地址不存在
     */
    @ExceptionHandler(NoResourceFoundException.class)
    public CommonResult<?> noResourceFoundExceptionHandler(NoResourceFoundException ex) {
        log.warn("[noResourceFoundExceptionHandler] {} {}", ex.getMessage(), ex.getResourcePath());
        return CommonResult.error(NOT_FOUND.getCode(), String.format("请求资源不存在:%s", ex.getResourcePath()));
    }

    /**
     * 处理 SpringMVC 请求方法不正确
     * 例如说，A 接口的方法为 GET 方式，结果请求方法为 POST 方式，导致不匹配
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public CommonResult<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
        log.warn("[httpRequestMethodNotSupportedExceptionHandler] {}", ex.getMessage());
        return CommonResult.error(METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确：%s", ex.getMessage()));
    }

    /**
     * 处理 SpringMVC 请求的 Content-Type 不正确
     * 例如说，A 接口的 Content-Type 为 application/json，结果请求的 Content-Type 为 application/octet-stream，导致不匹配
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public CommonResult<?> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException ex) {
        log.warn("[httpMediaTypeNotSupportedExceptionHandler] {}", ex.getMessage());
        return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求类型不正确：%s", ex.getMessage()));
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     * 例如说，接口上设置了 @RequestBody实体中 xx 属性类型为 Integer，结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public CommonResult<?> httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException ex) {
        return httpMessageConversionExceptionHandler(ex);
    }

    @ExceptionHandler(value = HttpMessageConversionException.class)
    public CommonResult<?> httpMessageConversionExceptionHandler(HttpMessageConversionException ex) {
        // 处理当 HTTP 请求参数转换尝试失败时由 HttpMessageConverter 抛出的异常。
        // 例如说前端传递的JSON格式错误，需要List<String>，但传递了逗号分隔的String。
        if (ex.getCause() instanceof InvalidFormatException invalidFormatException) {
            log.warn("[httpMessageConversionExceptionHandler] {}", ex.getMessage());
            return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误：%s", invalidFormatException.getValue()));
        }
        if (StrUtil.containsIgnoreCase(ex.getMessage(), "request body is missing")) {
            log.warn("[httpMessageConversionExceptionHandler] {}", ex.getMessage());
            return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误：body 缺失");
        }
        if (StrUtil.containsIgnoreCase(ex.getMessage(), "Invalid value")) {
            log.warn("[httpMessageConversionExceptionHandler] {}", ex.getMessage());
            return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误：值无效");
        }
        if (StrUtil.containsIgnoreCase(ex.getMessage(), "JSON parse error")) {
            log.warn("[httpMessageConversionExceptionHandler] {}", ex.getMessage());
            return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误：JSON 格式错误");
        }
        log.error("[httpMessageConversionExceptionHandler]", ex);
        return CommonResult.error(BAD_REQUEST.getCode(), "请求异常");
    }

    /**
     * 处理 Spring Security 权限不足的异常
     * 来源是，使用 @PreAuthorize 注解，AOP 进行权限拦截
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    public CommonResult<?> accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {
        log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({}) {}]", WebFrameworkUtils.getLoginUserId(req),
                req.getRequestURL(), ex.getMessage());
        if (StrUtil.equals(ex.getMessage(), "Access Denied")) {
            return CommonResult.error(FORBIDDEN.getCode(), "没有该页面的访问权限");
        }
        return CommonResult.error(FORBIDDEN.getCode(), ex.getMessage());
    }

    /**
     * 处理业务异常
     */
    @ExceptionHandler(value = KnownServiceException.class)
    public CommonResult<?> knownServiceExceptionHandler(KnownServiceException ex) {
        // 处理已知的业务异常，只打印较短的 StackTrace
        log.info("[knownServiceExceptionHandler] {} [{}]", ex.getMessage(), ExceptionUtil.stacktraceToString(ex, 250));
        return CommonResult.error(ex.getCode(), ex.getMessage());
    }

    /**
     * 处理业务异常
     */
    @ExceptionHandler(value = ServiceException.class)
    public CommonResult<?> serviceExceptionHandler(ServiceException ex) {
        // 处理未知的业务异常，打印较长的 StackTrace
        log.info("[yudaoServiceExceptionHandler] {}", ExceptionUtil.stacktraceToString(ex, 520));
        if (ex.getCause() instanceof ServiceException serviceException) {
            ex = serviceException;
        }
        return CommonResult.error(ex.getCode(), ex.getMessage());
    }

    /**
     * 处理系统异常，兜底处理所有的一切
     */
    @ExceptionHandler(value = Throwable.class)
    public CommonResult<?> defaultExceptionHandler(HttpServletRequest request, Throwable ex) {
        // 处理业务异常，视情况打印 StackTrace，主要是在服务层（XxxServiceImpl）抛出的异常
        Integer code = UNKNOWN.getCode();
        if (ex instanceof ServiceException) {
            code = ((ServiceException) ex).getCode();
        }
        if (IGNORE_ERROR_MESSAGES.contains(ex.getMessage())) {
            // 业务异常还是要记录堆栈才方便排查。不包含的时候，才进行打印，避免 ex 堆栈过多。
            // 即使打印，也只打印第一层 StackTraceElement，并且使用 warn 在控制台输出，更容易看到
            log.warn("[defaultExceptionHandler] {}", ExceptionUtil.stacktraceToString(ex, 250));
            return CommonResult.error(code, ex.getMessage());
        }
        // 适配 Hutool 的 Assert 类型断言之类的主动抛出的异常：若消息含中文，则不笼统的使用默认的“系统异常”
        if (Validator.hasChinese(ex.getMessage())) {
            log.warn("[defaultExceptionHandler] {}", ExceptionUtil.stacktraceToString(ex, 500));
            return CommonResult.error(code, ex.getMessage());
        }
        if (StrUtil.contains(ex.getClass().getName(), "org.springframework.dao.DataIntegrityViolationException")) {
            log.warn("数据完整性违规", ex);
            return CommonResult.error(BAD_REQUEST.getCode(), "请求参数值有问题，请检查");
        }
        if (StrUtil.contains(ex.getMessage(), "File not exist")) {
            log.warn("文件不存在：{}", ex.getMessage());
            return CommonResult.error(NOT_FOUND.getCode(), "文件不存在");
        }
        if (StrUtil.contains(ex.getMessage(), "No static resource")) {
            log.warn("资源不存在：{}", ex.getMessage());
            if (StrUtil.contains(ex.getMessage(), "sba")) {
                log.warn("[SpringBootAdmin] 已禁用，请检查`application*.yaml`中的配置项`spring.autoconfigure.exclude`");
                return CommonResult.error(NOT_FOUND.getCode(), "SpringBootAdmin 已禁用");
            }
            if (StrUtil.contains(ex.getMessage(), "-api/")) {
                return CommonResult.error(NOT_FOUND.getCode(), "接口不存在");
            }
            return CommonResult.error(NOT_FOUND.getCode(), "资源不存在");
        }
        if (StrUtil.contains(ex.getMessage(), "No OpenAPI resource found for group")) {
            log.warn("[Springdoc/Swagger] {}", ex.getMessage());
            return CommonResult.error(NOT_FOUND.getCode(), "接口分组不存在");
        }
        // 处理表不存在的异常
        CommonResult<?> tableNotExistsResult = handleTableNotExists(ex);
        if (tableNotExistsResult != null) {
            return tableNotExistsResult;
        }
        // 放在最后，兜底处理异常层层包裹的情况，优先获取异常最早发生的异常对象
        // java.lang.RuntimeException: ServiceException(super=cn.iocoder.yudao.framework.common.exception.KnownServiceException: 钱包余额不足, code=1007007001, message=钱包余额不足)
        Throwable rootCause = ExceptionUtil.getRootCause(ex);
        if (rootCause != null && !StrUtil.equals(rootCause.getClass().getName(), ex.getClass().getName())) {
            log.error("[defaultExceptionHandler] rootCause: {}", rootCause.getClass().getName());
            return allExceptionHandler(request, rootCause);
        }
        // 未知的异常，则记录日志，后续再排查处理
        log.error("[defaultExceptionHandler]", ex);
        // 插入异常日志
        this.createExceptionLog(request, ex);
        // 此处不宜暴露具体的错误信息给前端，直接统称“系统异常”之类的提示语，后端开发者可通过日志查看具体报错信息
        return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), "出错了！");
    }

    private void createExceptionLog(HttpServletRequest req, Throwable e) {
        // @Value 注解似乎注入不成功，所以使用 getProperty
        String apiErrorLogEnabled = SpringUtils.getProperty(PROPERTY_EXCEPTION_LOG_ENABLE, "false");
        log.info("[createExceptionLog][url({}) apiErrorLogEnabled({})]", req.getRequestURI(), apiErrorLogEnabled);
        // 插入错误日志
        ApiErrorLogCreateReqDTO errorLog = new ApiErrorLogCreateReqDTO();
        try {
            // 初始化 errorLog
            buildExceptionLog(errorLog, req, e);
            if (!BooleanUtil.toBoolean(apiErrorLogEnabled)) {
                log.warn("[createExceptionLog][url({}) exceptionName({}) exceptionClassName({}) exceptionMessage({}) 接口错误日志入库功能未启用]", req.getRequestURI(), errorLog.getExceptionName(), errorLog.getExceptionClassName(), errorLog.getExceptionMessage());
                return;
            }
            // 执行插入 errorLog
            ApiErrorLogCommonApi apiErrorLogApi = SpringUtil.getBean(ApiErrorLogCommonApi.class);
            if (apiErrorLogApi != null) {
                apiErrorLogApi.createApiErrorLogAsync(errorLog);
            } else {
                log.warn("[createExceptionLog][url({}) exceptionName({}) exceptionClassName({}) apiErrorLogApi is null]", req.getRequestURI(), errorLog.getExceptionName(), errorLog.getExceptionClassName());
            }
        } catch (Throwable th) {
            log.error("[createExceptionLog][url({}) log({}) 保存接口错误日志发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th);
        }
    }

    private void buildExceptionLog(ApiErrorLogCreateReqDTO errorLog, HttpServletRequest request, Throwable e) {
        // 处理用户信息
        errorLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
        errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request));
        // 设置异常字段
        errorLog.setExceptionName(e.getClass().getName());
        errorLog.setExceptionMessage(ExceptionUtil.getMessage(e));
        errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
        errorLog.setExceptionStackTrace(ExceptionUtil.stacktraceToString(e));
        StackTraceElement[] stackTraceElements = e.getStackTrace();
        Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
        StackTraceElement stackTraceElement = stackTraceElements[0];
        errorLog.setExceptionClassName(stackTraceElement.getClassName());
        errorLog.setExceptionFileName(stackTraceElement.getFileName());
        errorLog.setExceptionMethodName(stackTraceElement.getMethodName());
        errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber());
        // 设置其它字段
        String traceId = request.getHeader(HEADER_TRACE_ID);
        if (StrUtil.isBlank(traceId)) {
            traceId = TracerUtils.getTraceId();
        }
        errorLog.setTraceId(traceId);
        errorLog.setApplicationName(SpringUtils.getApplicationName());
        errorLog.setRequestUrl(request.getRequestURI());
        MapBuilder<String, Object> requestParams = MapUtil.builder();
        requestParams.put("query", ServletUtils.getParamMap(request));
        if (ServletUtils.isJsonRequest(request)) {
            try {
                requestParams.put("body", ServletUtils.getBody(new CacheRequestBodyWrapper(request)));
            } catch (Exception ex) {
                log.info("[buildExceptionLog][requestUri({}) contentType({}) body 解析异常: {}]", request.getRequestURI(), request.getContentType(), ex.getMessage());
            }
        } else {
            requestParams.put("body", "[Not JSON]");
        }
        errorLog.setRequestParams(JsonUtils.toJsonString(requestParams.build()));
        errorLog.setRequestMethod(request.getMethod());
        errorLog.setUserAgent(ServletUtils.getUserAgent(request));
        errorLog.setUserIp(ServletUtils.getClientIP(request));
        errorLog.setExceptionTime(LocalDateTime.now());
    }

    /**
     * 处理 Table 不存在的异常情况
     *
     * @param ex 异常
     * @return 如果是 Table 不存在的异常，则返回对应的 CommonResult
     */
    private CommonResult<?> handleTableNotExists(Throwable ex) {
        String message = ExceptionUtil.getRootCauseMessage(ex);
        if (!message.contains("doesn't exist")) {
            return null;
        }
        log.warn("handleTableNotExists: {}", message);
        // 1. 数据报表
        if (message.contains("report_")) {
            log.error("[报表模块 yudao-module-report - 表结构未导入]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[报表模块 yudao-module-report - 表结构未导入]");
        }
        // 2. 工作流
        if (message.contains("bpm_")) {
            log.error("[工作流模块 yudao-module-bpm - 表结构未导入]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[工作流模块 yudao-module-bpm - 表结构未导入]");
        }
        // 3. 微信公众号
        if (message.contains("mp_")) {
            log.error("[微信公众号 yudao-module-mp - 表结构未导入]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[微信公众号 yudao-module-mp - 表结构未导入]");
        }
        // 4. 商城系统
        if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) {
            log.error("[商城系统 yudao-module-mall - 表结构未导入]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[商城系统 yudao-module-mall - 表结构未导入]");
        }
        // 5. ERP 系统
        if (message.contains("erp_")) {
            log.error("[ERP 系统 yudao-module-erp - 表结构未导入]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[ERP 系统 yudao-module-erp - 表结构未导入]");
        }
        // 6. CRM 系统
        if (message.contains("crm_")) {
            log.error("[CRM 系统 yudao-module-crm - 表结构未导入]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[CRM 系统 yudao-module-crm - 表结构未导入]");
        }
        // 7. 支付平台
        if (message.contains("pay_")) {
            log.error("[支付模块 yudao-module-pay - 表结构未导入]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[支付模块 yudao-module-pay - 表结构未导入]");
        }
        // 8. AI 大模型
        if (message.contains("ai_")) {
            log.error("[AI 大模型 yudao-module-ai - 表结构未导入]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[AI 大模型 yudao-module-ai - 表结构未导入]");
        }
        // 9. IOT 物联网
        if (message.contains("iot_")) {
            log.error("[IOT 物联网 yudao-module-iot - 表结构未导入]");
            return CommonResult.error(NOT_IMPLEMENTED.getCode(),
                    "[IOT 物联网 yudao-module-iot - 表结构未导入]");
        }
        return null;
    }

}
